[Python] 파이썬 비동기 프로그래밍 기초(1)
비동기 프로그래밍이란?
비동기 프로그래밍이란 말 그대로 “순차적으로 진행되지 않는” 처리 방식입니다. 기존의 동기적 프로그래밍은 작업을 순차적으로 실행하고 한 작업이 끝날 때까지 다음 작업을 시작하지 않습니다.
예를 들어 함수를 호출하면 해당 함수가 완료될 때까지 대기하고 반환된 값을 받아옵니다. 그와 다르게 비동기적 프로그래밍에서는 작업을 호출하고 결과를 기다리는 동안 다른 작업을 수행할 수 있게 됩니다.
원래 파이썬은 동기적으로 작동하도록 설계된 프로그램이기 때문에 비동기 프로그래밍이 어색했는데요. 하지만 파이썬에서도 asycio 라이브러리가 표준으로 채택되면서 비동기 프로그래밍을 쉽게 구현할 수 있게 되었습니다.
파이썬에서 비동기 프로그래밍을 사용하면 시스템의 효율성을 크게 향상시킬 수 있습니다. 특히 I/O 작업(파일 읽기/쓰기, 네트워크 요청 등)에 있어서 그 효과가 큽니다.
이런 작업들은 컴퓨터의 CPU보다는 외부 시스템에 의존하기 때문에 기다리는 동안에 CPU는 다른 일을 할 수 있습니다. 비동기 프로그래밍은 이런 시간을 활용하여 작업의 효율을 향상시킵니다.
앞서 말했듯 파이썬에서는 asyncio 라는 라이브러리를 통해 비동기 프로그래밍을 지원합니다. 이 asyncio는 이벤트 루프라는 것을 통해 여러 작업을 동시에 관리합니다.
간단한 예시를 들어보겠습니다.
import asyncio
async def main():
print('Hello')
await asyncio.sleep(1)
print('World')
asyncio.run(main())
위 코드에서 main 함수는 async 키워드를 사용하여 비동기 함수로 선언되었습니다. 비동기 함수 내에서는 await 키워드를 사용하여 비동기 작업을 기다릴 수 있습니다. 이 경우에는 1초 동안 기다리는 작업을 await 합니다.
결과는 예상대로 위와 같이 나옵니다. 그렇지만 이 예시만 보면 time.sleep(1)과 어떤 차이인지 잘 감이 오지 않는데요.
그 차이점을 이해하기 위해서는 우선 CPU의 작업 방식과 파이썬의 ‘time.sleep’, ‘asyncio.sleep’이 어떻게 작동하는지 알아야 합니다.
time.sleep(1)을 사용하면 파이썬은 현재 스레드에서 1초 동안 아무 것도 하지 않고 대기하게 됩니다. 그 시간 동안 CPU는 다른 일을 할 수 없으므로 시간이 낭비됩니다.
반면에 asyncio.sleep(1)을 사용하면, 파이썬은 “이 작업을 1초 후에 완료하겠다”라고 예약하고 그 사이에 CPU는 다른 작업을 수행할 수 있습니다.
따라서 CPU 시간을 더 효율적으로 사용할 수 있습니다.
비동기 프로그래밍이 유용한 경우를 예로 들어 보겠습니다.
import time
def job(number):
print(f'Starting job {number}')
time.sleep(1)
print(f'Finishing job {number}')
start = time.time()
for i in range(5):
job(i)
end = time.time()
print("전체 소요시간 : ", end - start)
위 코드는 순차적으로 5개의 작업을 수행합니다. 각 작업은 1초 동안 대기하기 때문에 전체 코드의 실행 시간은 아래와 같이 5초입니다.
하지만 만약 이러한 작업이 서로 독립적이라면 더 빠르게 실행되지 않을까요? asycio를 활용하여 비동기적으로 코드를 작성해보겠습니다.
import asyncio
import time
async def job(number):
print(f'Starting job {number}')
await asyncio.sleep(1)
print(f'Finishing job {number}')
async def main():
tasks = [job(i) for i in range(5)]
await asyncio.gather(*tasks)
start = time.time()
asyncio.run(main())
end = time.time()
print("전체 소요시간 : ", end - start)
이 코드는 각 작업을 동시에 시작하고 모든 작업이 완료될 때까지 기다립니다.
asyncio.run(main())는 이벤트 루프를 시작하고, main 함수를 실행하는 역할을 합니다.
이벤트 루프가 실행되는 동안 main 함수에서 await 하는 작업이 완료될 때까지 기다리게 되고 그 다음 코드를 실행합니다.
각 작업은 독립적으로 1초 동안 대기하지만 모든 작업이 동시에 실행되므로 전체 코드의 실행 시간은 약 1초입니다. 이렇게 비동기 프로그래밍은 프로그램의 효율을 크게 향상시킬 수 있습니다.
여기서 살짝 이해가 되지 않는 부분은 이벤트 루프가 실행되는 동안 main 함수에서는 왜 await 작업을 기다리게 되는가? 기다리지 않고 다음 작업을 하는게 비동기 프로그래밍 아닌가? 이런 의문이 들 수도 있는데요. 더 자세한 공부는 다음 포스팅에서 이어가보도록 하겠습니다.
Leave a comment